﻿function Get-NewManifesObject
{
    [cmdletbinding(DefaultParameterSetName="none")]
    Param(
        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [PsObject] 
        $sourceObject,

        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [PsObject] 
        $targetObject,

        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [PsObject] 
        $PlatformConfiguration,

        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [String] 
        $ParentLevel
         
    )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Get-NewManifesObject from sourceObject $($sourceObject.ElementType) identified by  $($sourceObject.UniqueKey)"
    }
    End
    {
        write-verbose "End Get-NewManifesObject from sourceObject $($sourceObject.ElementType) identified by  $($sourceObject.UniqueKey)"
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }
    Process
    {

        switch($sourceObject.ElementType)
        {
            "folder"{$newObject = New-ManifestFolder}
            "file"{$newObject = New-ManifestFile}
            "product"{$newObject = New-ManifestProduct}
            "group"{$newObject = New-ManifestGroup}

            default{$newObject = New-Object PSObject}
        }

        $newObject | Add-Member -MemberType NoteProperty -Name Parent -Value $targetObject
        if ($sourceObject.Template)
        {
            Write-Verbose "Source object has a template named '$($sourceObject.Template)'"

            #Retrieve the template from templates collection loaded in the platform configuration
            $template = Find-Template -Templates $PlatformConfiguration.Templates -TemplateType $sourceObject.ElementType -TemplateUniqueKey $sourceObject.Template
            if (-not $template) {throw "Template named '$($sourceObject.Template)' of element type '$($sourceObject.ElementType)' doesn't exist in the products templates"}

            if ($sourceObject.ElementType -eq 'product')
            {
                $productTemplate = Get-TemplateProduct_v2 -Template $template  -Version $sourceObject.version -Architecture $sourceObject.architecture -PackageType $sourceObject.packageType
                Merge-MemberToManifestObject -sourceObject $productTemplate -targetObject $newObject -PlatformConfiguration $PlatformConfiguration
            }
            else
            {
                Merge-MemberToManifestObject -sourceObject $template -targetObject $newObject -PlatformConfiguration $PlatformConfiguration
            }
        }

        Merge-MemberToManifestObject -sourceObject $sourceObject -targetObject $newObject -PlatformConfiguration $PlatformConfiguration

        $newObject| Add-Member -MemberType NoteProperty Level -Value $ParentLevel -Force
        
        $newObject       

    }
}


function Merge-MemberToManifestObject
{
   [cmdletbinding(DefaultParameterSetName="none")]
    Param( 
        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [PsObject] 
        $sourceObject,

        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [PsObject] 
        $targetObject,

        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [PsObject] 
        $PlatformConfiguration,


        [Parameter(Mandatory=$false,ValueFromPipeline=$false)]
        [ScriptBlock] 
        $Filter = {(($_.name -ne 'BindingSource') -and ($_.name -ne 'BindingCollections'))}

        )
    Process
    {
        $sourceObject | Get-Member -MemberType NoteProperty | ? $Filter | ForEach-Object {

            $shortDefinition = ($_.Definition -split " ")[0].Trim()
            #Fix Powershell V5 / V4
            $shortDefinition = $shortDefinition.replace("System.Object[]","Object[]")
            $shortDefinition = $shortDefinition.replace("System.Collections.Hashtable","hashtable")

            $name = $_.Name
            Write-Verbose "Identiy property named '$name' with definition type '$shortDefinition'"

            switch($shortDefinition)
            {
                "object[]" {
                    foreach($childElement in $sourceObject.$name)
                    {
                        $value = Get-NewManifesObject -sourceObject $childElement -targetObject $targetObject -PlatformConfiguration $PlatformConfiguration -ParentLevel $sourceObject.ElementType
                        Push-ObjectToCollection -Container $targetObject -PropertyName $Name -CurrentObject $value                      
                    }

                }
                "hashtable" {
                    $keys = $sourceObject.$name.keys
                    foreach($key in $keys)
                    {
                        $value = Get-NewManifesObject -sourceObject $sourceObject.$name[$key] -targetObject $targetObject -PlatformConfiguration $PlatformConfiguration -ParentLevel $sourceObject.ElementType
                        Push-ObjectToCollection -Container $targetObject -PropertyName $Name -CurrentObject $value 
                    }

                }
                "System.Management.Automation.PSCustomObject" {
                    $value = Get-NewManifesObject -sourceObject $sourceObject.$name -targetObject $targetObject -PlatformConfiguration $PlatformConfiguration -ParentLevel $sourceObject.ElementType
                    $targetObject | Add-Member -MemberType NoteProperty -Name $name -Value $value
                }
                default {
                    #Add-MemberToManifestObject -ManifestObject $targetObject -MemberName $name -MemberValue $sourceObject.$name
                    $targetObject | Add-Member -MemberType NoteProperty -Name $name -Value $sourceObject.$name -Force                    
                }
            }
            
        }

        #Add the script property == Add binding Value
        $sourceObject | Get-Member -MemberType ScriptProperty | ForEach-Object {
            $shortDefinition = ($_.Definition -split " ")[0].Trim()
            $name = $_.Name
            Write-Verbose "Identiy property named '$name' with definition type '$shortDefinition'"
            
            $targetObject | Add-Member -MemberType NoteProperty -Name $name -Value $sourceObject.$name
            
        }

    }

}


<#
.SYNOPSIS
Add a generic Object to the Container element if this current element has the collection property 

.DESCRIPTION
Add a generic Object to the current element if this current element has the collection property 
The collection Property can be a simple collection or a dictionary list

.PARAMETER Container
Required. The container object which can receive the current Object if it contains the collection

.PARAMETER PropertyName
Required. The collection property name to use

.PARAMETER CurrentObject
Required. The current object to add to the collection
#>
function Add-ObjectToCollection
{
    
    [cmdletbinding(DefaultParameterSetName="none")]
    Param( 
        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [PSObject]$Container,

        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [String]$PropertyName,

        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [PSObject]$CurrentObject,

        [Parameter(ParameterSetName="DiscardMultipleKeys",Mandatory=$true,ValueFromPipeline=$false)]
        [Switch]$DiscardMultipleKeys,

        [Parameter(ParameterSetName="Merge",Mandatory=$true,ValueFromPipeline=$false)]
        [Switch]$Merge,

        [Parameter(ParameterSetName="Merge",Mandatory=$true,ValueFromPipeline=$false)]
        [String]$MergeProperty,

        [Parameter(ParameterSetName="Merge",Mandatory=$true,ValueFromPipeline=$false)]
        [String]$MergeSeparator=";"
    )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Add-ObjectToCollection to collection $PropertyName for object $($CurrentObject.ElementType) identified by  $($CurrentObject.UniqueKey) on Container $($Container.ElementType) identified by  $($Container.UniqueKey)"
    }
    End
    {
        write-verbose "End Add-ObjectToCollection to collection $PropertyName for object $($CurrentObject.ElementType) identified by  $($CurrentObject.UniqueKey) on Container $($Container.ElementType) identified by  $($Container.UniqueKey)"
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }    
    Process
    {

        #If the container does't have this collection, exit the function
        if ($Container.$($PropertyName) -eq $null) {return}

        #If the current Object already have a container and if the Container is not a server. Exit the function
        if (($CurrentObject.Container) -and ($Container.ElementType -ne 'Server')) {return}

        #retrieve the type of collection and add the element
        $collectionType = $Container.$($PropertyName).GetType()
        switch($collectionType.Name.ToLower())
        {
            "hashtable"
            {
                if ($Container.$($PropertyName).containsKey($CurrentObject.UniqueKey))
                {
                    if ($PSCmdlet.ParameterSetName -eq "Merge")
                    {
                        $existingObject = $Container.$($PropertyName).$($CurrentObject.UniqueKey)
                        $existingProperty = $existingObject.$($MergeProperty)
                        $CurrentProperty = $CurrentObject.$($MergeProperty)

                        if (($existingProperty -ne $null) -and ($CurrentProperty -ne $null))
                        {
                            $existingObject.$($MergeProperty) = "$existingProperty$MergeSeparator$CurrentProperty"
                        }
                        else
                        {
                            $existingObject.$($MergeProperty) = "$existingProperty$CurrentProperty"
                        }
                    }
                    else                        
                    {
                        if (-not $DiscardMultipleKeys)
                        {
                            throw New-Object System.Exception "An Object with uniqueKey $($CurrentObject.UniqueKey) already exists in the Dictionnary $PropertyName for object $($CurrentObject.ElementType) identified by  $($CurrentObject.UniqueKey) on Container $($Container.ElementType) identified by  $($Container.UniqueKey)"
                        }
                    }
                }
                else
                {    
                    $Container.$($PropertyName).Add($CurrentObject.UniqueKey, $CurrentObject)
                }
            }
            "object[]" 
            {
                $Container.$($PropertyName) += $CurrentObject
            }
            default {throw New-Object System.Exception "Collection of $collectionType is not managed in Add-ObjectToCOllection cmdlet"}
        }

        if (-not $CurrentObject.Container)
        {
            $currentObject | Add-Member -MemberType NoteProperty -Name Container -Value $container
        }
        else
        {
            if ($Container.ElementType -ne 'Server')
            {
                if (($CurrentObject.Container.ElementType -ne $Container.ElementType) -and ($CurrentObject.Container.UniqueKey -ne $Container.UniqueKey))
                {
                    throw New-Object System.Exception "Try to add the same object to tow differents Containers. This behavior is not expected"    
                }
            }
        }


    }


}

<#
.SYNOPSIS
Push a generic Object to the firts Container element which have the expected collection property 
and add the generic object to the server which is the final collection conatiner if the server has the collection

.DESCRIPTION
Add a generic Object to the current element if this current element has the collection property 
The collection Property can be a simple collection or a dictionary list

.PARAMETER Container
Required. The container object which can receive the current Object if it contains the collection

.PARAMETER PropertyName
Required. The collection property name to use

.PARAMETER CurrentObject
Required. The current object to add to the collection
#>
function Push-ObjectToCollection
{
    
    [cmdletbinding(DefaultParameterSetName="none")]
    Param( 
        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [PSObject]$Container,

        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [String]$PropertyName,

        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [PSObject]$CurrentObject,

        [Parameter(ParameterSetName="DiscardMultipleKeys",Mandatory=$true,ValueFromPipeline=$false)]
        [Switch]$DiscardMultipleKeys

    )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Push-ObjectToCollection to collection $PropertyName for object $($CurrentObject.ElementType) identified by  $($CurrentObject.UniqueKey) on Current $($Container.ElementType) identified by  $($Container.UniqueKey)"
    }
    End
    {
        write-verbose "End Push-ObjectToCollection to collection $PropertyName for object $($CurrentObject.ElementType) identified by  $($CurrentObject.UniqueKey) on Current $($Container.ElementType) identified by  $($Container.UniqueKey)"
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }    
    Process
    {

        $CurrentContainer = $Container
        while ($CurrentContainer -ne $null)
        {
            if ($PSCmdlet.ParameterSetName -eq "DiscardMultipleKeys")
            {
                Add-ObjectToCollection -Container $CurrentContainer -PropertyName $PropertyName -CurrentObject $CurrentObject -DiscardMultipleKeys
            }
            else
            {
                Add-ObjectToCollection -Container $CurrentContainer -PropertyName $PropertyName -CurrentObject $CurrentObject
            }
            
            
			<#
            if (!$CurrentContainer.Parent -and $CurrentContainer.ElementType -ne "server")
            {
                 write-warning "end Push-ObjectToCollection to collection $PropertyName for object $($CurrentObject.ElementType) identified by  $($CurrentObject.UniqueKey) on Container $($Container.ElementType) identified by  $($Container.UniqueKey)"
                 write-warning "end on Container $($CurrentContainer.ElementType) identified by  $($CurrentContainer.UniqueKey)"
            }
			#>
            $CurrentContainer = $CurrentContainer.Parent
        }

    }


}

